﻿using System;
using System.IO;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MimeKit;

namespace CK.Sprite.MessageComponent
{
    public class SmtpService : ISmtpService
    {
        private readonly SmtpOptions options;
        private readonly ILogger<SmtpService> logger;

        public SmtpService(
            IOptions<SmtpOptions> options,
            ILogger<SmtpService> logger
        )
        {
            this.options = options.Value;
            this.logger = logger;
        }

        private bool CertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            logger.LogError(
                "SMTP Server's certificate {CertificateSubject} issued by {CertificateIssuer} with thumbprint {CertificateThumbprint} and expiration date {CertificateExpirationDate} is considered invalid with {SslPolicyErrors} policy errors",
                certificate.Subject,
                certificate.Issuer,
                certificate.GetCertHashString(),
                certificate.GetExpirationDateString(),
                sslPolicyErrors);

            if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) && chain?.ChainStatus != null)
                foreach (var chainStatus in chain.ChainStatus)
                    logger.LogError("Status: {Status} - {StatusInformation}", chainStatus.Status, chainStatus.StatusInformation);

            return false;
        }

        public async Task<SendResultEntity> SendEmailAsync(MailEntity mailEntity, CancellationToken cancellationToken = default)
        {
            var sendResultEntity = new SendResultEntity();
            using (var client = new SmtpClient())
            {
                client.ServerCertificateValidationCallback = CertificateValidationCallback;

                await ConnectionAsync(client, sendResultEntity, cancellationToken);

                // Note: since we don't have an OAuth2 token, disable
                // the XOAUTH2 authentication mechanism.
                client.AuthenticationMechanisms.Remove("XOAUTH2");

                if (options.RequireCredentials)
                {
                    if (options.UseDefaultCredentials)
                    {
                        // There's no notion of 'UseDefaultCredentials' in MailKit, so empty credentials are passed in.
                        await AuthenticateAsync(string.Empty, string.Empty, client, sendResultEntity, cancellationToken);
                    }
                    else if (!string.IsNullOrWhiteSpace(options.UserName))
                    {
                        await AuthenticateAsync(options.UserName, options.Password, client, sendResultEntity, cancellationToken);
                    }
                }

                await SendAsync(mailEntity, client, sendResultEntity, cancellationToken);
                await client.DisconnectAsync(true, cancellationToken);
            }

            return sendResultEntity;
        }

        private async Task ConnectionAsync(SmtpClient client, SendResultEntity sendResultEntity, CancellationToken cancellationToken)
        {
            try
            {
                await client.ConnectAsync(options.Host, options.Port, options.SecureSocketOptions, cancellationToken);
            }
            catch (SmtpCommandException ex)
            {
                sendResultEntity.ResultInformation = $"尝试连接时出错:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"尝试连接时的协议错误:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"服务器连接错误:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
        }

        private async Task SendAsync(MailEntity mailEntity, SmtpClient client, SendResultEntity sendResultEntity, CancellationToken cancellationToken)
        {
            try
            {
                if(string.IsNullOrEmpty(mailEntity.FromAddress))
                {
                    mailEntity.From = options.DefaultSender;
                    mailEntity.FromAddress = options.DefaultSenderAddress;
                }
                await client.SendAsync(MailMessageHelper.AssemblyMailMessage(mailEntity), cancellationToken);
            }
            catch (SmtpCommandException ex)
            {
                switch (ex.ErrorCode)
                {
                    case SmtpErrorCode.RecipientNotAccepted:
                        sendResultEntity.ResultInformation = $"收件人未被接受:{ex.Message}";
                        break;
                    case SmtpErrorCode.SenderNotAccepted:
                        sendResultEntity.ResultInformation = $"发件人未被接受:{ex.Message}";
                        break;
                    case SmtpErrorCode.MessageNotAccepted:
                        sendResultEntity.ResultInformation = $"消息未被接受:{ex.Message}";
                        break;
                }
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"发送消息时的协议错误:{ex.Message}";
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"邮件接收失败:{ex.Message}";
                sendResultEntity.ResultStatus = false;
            }
        }

        private async Task AuthenticateAsync(string userName, string password, SmtpClient client, SendResultEntity sendResultEntity, CancellationToken cancellationToken)
        {
            try
            {
                await client.AuthenticateAsync(userName, password, cancellationToken);
            }
            catch (AuthenticationException ex)
            {
                sendResultEntity.ResultInformation = $"无效的用户名或密码:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpCommandException ex)
            {
                sendResultEntity.ResultInformation = $"尝试验证错误:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"尝试验证时的协议错误:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"账户认证错误:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
        }
    }
}